package com.sweet.auth;

import com.sweet.enums.Charset;
import com.sweet.json.JsonUtil;
import com.sweet.lang.StringUtil;
import com.sweet.util.WebUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Base64;

/**
 * Basic认证
 *
 * @author limi
 * @create 2018-03-23-15:49
 */
@Slf4j
public final class BasicAuth {
    /**
     * 认证类型为Basic
     */
    private static final String AUTH_TYPE = "Basic";

    /**
     * 认证失败状态码
     */
    private static final int AUTH_FAILURE_CODE = 401;

    private BasicAuth() {
    }

    /**
     * basic模板
     */
    @Data
    public static class BasicVO {
        private String username;
        private String password;

        public BasicVO() {
        }

        public BasicVO(String username, String password) {
            this.username = username;
            this.password = password;
        }
    }

    /**
     * 获取basic信息
     *
     * @param request
     * @return
     */
    public static BasicVO getBasic(HttpServletRequest request) {
        //获取认证请求，authInfo格式为：Basic YWRtaW46MTIzNDU2，必须包含basic和空格，因此长度必须大于6
        String authorization = getAuthorization(request);
        if (StringUtil.isBlank(authorization) || authorization.length() < 6) {
            log.debug("请求时未传递认证信息，拒绝访问!");
            return null;
        }

        //拆分信息
        String[] splitInfo = StringUtil.split(authorization, " ");
        if (splitInfo == null || splitInfo.length != 2) {
            log.debug("请求时有传递认证信息，但信息分析失败，拒绝访问!");
            return null;
        }

        //获取认证类型，认证信息
        String authType = splitInfo[0];
        String authInfo = splitInfo[1];
        if (StringUtil.isBlank(authType) || StringUtil.isBlank(authInfo)) {
            log.debug("请求时有传递认证信息，但信息分析失败，拒绝访问!");
            return null;
        }

        //验证是否为basic认证
        if (!authType.equalsIgnoreCase(AUTH_TYPE)) {
            log.debug("认证类型为{}，系统仅支持{}，拒绝访问!", authType, AUTH_TYPE);
            return null;
        }

        //解码认证信息
        String[] account;
        try {
            //解码认证信息，解码后格式为username:password
            account = StringUtil.split(new String(Base64.getDecoder().decode(authInfo)), ":");
        } catch (Exception e) {
            account = null;
        }

        if (account == null || account.length != 2) {
            log.debug("认证信息为{}，通过base64解码失败，拒绝访问!", authInfo);
            return null;
        }

        //定义basic
        return new BasicVO(account[0], account[1]);
    }

    /**
     * basic认证
     *
     * @param request       本次请求
     * @param checkUsername 要检查的用户名
     * @param checkPassword 要检查的密码
     * @return
     */
    public static boolean auth(HttpServletRequest request, String checkUsername, String checkPassword) {
        //获取basic
        BasicVO basic = getBasic(request);
        if (basic == null || basic.getUsername() == null || basic.getPassword() == null) {
            log.debug("认证信息为空，拒绝访问!");
            return false;
        }

        if (basic.getUsername().equals(checkUsername) && basic.getPassword().equals(checkPassword)) {
            log.debug("认证信息匹配，允许访问!");
            return true;
        }

        log.debug("认证信息错误({}:{})，拒绝访问!", basic.getUsername(), basic.getPassword());
        return false;
    }

    /**
     * 返回认证失败信息
     *
     * @param request  请求
     * @param response 响应
     * @param message  反馈信息
     * @param printLog 是否打印日志
     */
    public static void fail(HttpServletRequest request, HttpServletResponse response, Object message, boolean printLog) throws IOException {
        //输出警告日志
        if (printLog) {
            log.warn("Basic认证失败，认证信息({})!", getAuthorization(request));
        }

        //输出认证响应信息
        WebUtil.write(getFailResponse(response), JsonUtil.toJson(message), Charset.UTF8);
    }

    /**
     * 获取请求认证信息
     *
     * @param request
     * @return
     */
    private static String getAuthorization(HttpServletRequest request) {
        return request.getHeader("Authorization");
    }

    /**
     * 设置认证响应信息
     *
     * @param response
     * @return
     */
    public static HttpServletResponse getFailResponse(HttpServletResponse response) {
        //设置状态码、认证头，反馈给浏览器弹窗
        response.setStatus(AUTH_FAILURE_CODE);
        response.addHeader("WWW-Authenticate", "Basic Realm=\"\"");
        return response;
    }
}